package ComoViajo;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;


/**
 * representacion de la red de transporte
 * 
 * @author eugenio
 * 
 */
public class RedDeTransporte {
	public ArrayList<ArrayList<Estacion>> ramales = new ArrayList<ArrayList<Estacion>>();

	/**
	 * corte de control para la busqueda de viajes
	 */
	private int limiteDeCombinaciones=1;
	
	/**
	 * Dada una estacion encuentra su ramal
	 * @author eugenio
	 * @param estacion que buscamos
	 * @return el ramal es un arraylist de estaciones
	 * @throws BusquedaException en caso de que no encuentre nada
	 */
	public ArrayList<Estacion> getRamal(Estacion estacion)
			throws BusquedaException {
		Estacion laEstacion;
		ArrayList<Estacion> elRamal;

		Iterator<ArrayList<Estacion>> ramalesIterator = this.ramales.iterator();
		Iterator<Estacion> estacionesIterator;

		// encontrar el ramal y dejarlo en la variable ramal
		while (ramalesIterator.hasNext()) {
			elRamal = ramalesIterator.next();

			estacionesIterator = elRamal.iterator();
			while (estacionesIterator.hasNext()) {
				laEstacion = estacionesIterator.next();
				if (laEstacion.IsEqual(estacion)) {
					return elRamal;
				}
			}
		}

		// si no encontro el ramal
		throw new BusquedaException("no se encontro ramal");

	}

	/**
	 * dada una estacion la encuentra ramal.estaciones
	 * 
	 * @author eugenio
	 * @param estacion que buscamos
	 * @return estacion cons sus respectivas referencias
	 * @throws BusquedaException en caso de no encontrar la estacion
	 */
	private Estacion getEstacion(Estacion estacion) throws BusquedaException {
		Estacion laEstacion;
		Collection<Estacion> elRamal;

		Iterator<ArrayList<Estacion>> ramalesIterator = this.ramales
				.iterator();
		Iterator<Estacion> estacionesIterator;

		// encontrar el ramal y dejarlo en la variable ramal
		while (ramalesIterator.hasNext()) {
			elRamal = ramalesIterator.next();

			estacionesIterator = elRamal.iterator();
			while (estacionesIterator.hasNext()) {
				laEstacion = estacionesIterator.next();
				if (laEstacion.IsEqual(estacion)) {
					return laEstacion;
				}
			}
		}

		// si no encontro el ramal
		throw new BusquedaException("no se encontro estacion la estacion");

	}

	/**
	 * tanto el origen como el destino pueden instansearse desde cualquier parte
	 * lo importante es que el nombre del ramal y el nombre de la estacion de
	 * destino sean conocidas por el pasajero
	 * 
	 * @author eugenio
	 * @param origen
	 * @param destino
	 * @return una collection de viajes, que tiene una coleccion de recorridos,
	 *         los recorridos son una collection de objetos Estacion
	 * @throws BusquedaException 
	 * @throws ComoViajeException 
	 * @throws Exception
	 */
	public ArrayList<Viaje> getViajes(Estacion origen, Estacion destino) throws BusquedaException, ComoViajeException {
		origen = this.getEstacion(origen);
		destino = this.getEstacion(destino);
			
		ArrayList<Viaje> viajesQueRetorna = new ArrayList<Viaje>();
	
		this.recorrerRamalHaciaAdelante(origen, destino, new Viaje(), origen, viajesQueRetorna);
		this.recorrerRamalHaciaAtras(origen, destino, new Viaje(), origen, viajesQueRetorna);
		this.recorrerEstacionesConectadasDelOrigen(origen, destino, viajesQueRetorna);
		
		return viajesQueRetorna;
	}

	/**
	 * tanto el origen como el destino pueden instansearse desde cualquier parte
	 * lo importante es que el nombre del ramal y el nombre de la estacion de
	 * destino sean conocidas por el pasajero
	 * 
	 * @author eugenio
	 * @param origen
	 * @param destino
	 * @return una collection de viajes, que tiene una coleccion de recorridos,
	 *         los recorridos son una collection de objetos Estacion
	 * @throws BusquedaException 
	 * @throws ComoViajeException 
	 * @throws Exception
	 */
	public ArrayList<Viaje> getViajes(Estacion origen, Estacion destino,int cantidadMaximoDeCombinaciones) throws BusquedaException, ComoViajeException {
		origen = this.getEstacion(origen);
		destino = this.getEstacion(destino);
		
		// copiamas a un auxiliar la cantidad maxima de combinaciones y la seteamos en nuestra variable
		int copiaLimiteMaximoCombinaciones=this.limiteDeCombinaciones;
		this.limiteDeCombinaciones=cantidadMaximoDeCombinaciones;
			
		ArrayList<Viaje> viajesQueRetorna = new ArrayList<Viaje>();
	
		this.recorrerRamalHaciaAdelante(origen, destino, new Viaje(), origen, viajesQueRetorna);
		this.recorrerRamalHaciaAtras(origen, destino, new Viaje(), origen, viajesQueRetorna);
		this.recorrerEstacionesConectadasDelOrigen(origen, destino, viajesQueRetorna);
		
		// restauramos el maximo de combinaciones
		this.limiteDeCombinaciones=copiaLimiteMaximoCombinaciones;
		
		// devolvemos los viajes
		return viajesQueRetorna;
	}

	/**
	 * @author eugenio
	 * @param origen
	 * @param destino
	 * @param losViajes arraylist de viajes de estacion origen a detino
	 * @throws BusquedaException en caso de no encontrar la estacion origen o destino
	 * @throws ComoViajeException
	 */
	public void recorrerEstacionesConectadasDelOrigen(Estacion origen, Estacion destino, Collection<Viaje> losViajes) throws BusquedaException, ComoViajeException {
		if(origen.IsEqual(destino)){
			return; 
		}
	
		Viaje elViaje = new Viaje();		
		Recorrido recorrido = elViaje.nuevoRecorrido(origen);
		recorrido.estaciones.add(origen);
				
		this.recorrerEstacionesConectadas(origen,destino,recorrido,origen,elViaje,losViajes);
		
	}


	/**
	 * @author eugenio
	 * @param elViaje
	 * @return true si se debe realizar el corte de control que recorre los viajes
	 */
	private boolean corteDeControlGetViajes(Viaje elViaje){
		// corte de control
		if(elViaje.recorridos.size()>limiteDeCombinaciones) return true;
		return false;
	}
	
	
	/**
	 * Metodo que recorre las estaciones conectadas a una estacion
	 * 
	 * @author eugenio
	 * @param origen estacion origen
	 * @param destino estacion destino
	 * @param elViaje el viaje que se esta realizando, un arraylist de un arraylist  de estaciones
	 * @param estacionEnQueNosEncontramos estacion en la que estamos actualmente
	 * @param losViajes conjunto de viajes posibles que van de origen a destino
	 * @throws BusquedaException
	 * @throws ComoViajeException 
	 */
	private void recorrerEstacionesConectadas(Estacion origen, Estacion destino,Recorrido recorrido,Estacion estacion, Viaje elViaje, Collection<Viaje> losViajes) throws BusquedaException, ComoViajeException{	
		if(this.corteDeControlGetViajes(elViaje)) {
			return;
		}
				
		Viaje nuevoViaje;
				
		// iterado que recorrer las estacionesconectadas a una estacion
		Iterator<Estacion> estacionesConectadasIterator;
		Estacion estacionConectada;
		Recorrido recorridoCopiaAuxiliar;

		estacionesConectadasIterator=estacion.estacionesConectadas.iterator();
		
		while (estacionesConectadasIterator.hasNext()) {
			estacionConectada = estacionesConectadasIterator.next();
				
			// corte de control
			if(estacionConectada.IsEqual(destino)){
				nuevoViaje = elViaje.clone();
				nuevoViaje.recorridos.add(recorrido);
				recorrido=nuevoViaje.nuevoRecorrido(estacionConectada);
				recorrido.estaciones.add(estacionConectada);
				nuevoViaje.recorridos.add(recorrido);
				
				//antes de agregar el viaje realizamos el corte de control nuevamente
				if(this.corteDeControlGetViajes(nuevoViaje)) {
					return;
				}
						
				
				// agregamos el viaje al array que tiene todos los viajes posibles
				losViajes.add(nuevoViaje);
				return;
			}
			
			// realizamos el recorrido hacia adelante en un ramal de una estacion conectada
			nuevoViaje = elViaje.clone();
			nuevoViaje.recorridos.add(recorrido);
			recorridoCopiaAuxiliar=recorrido;
			recorrido=elViaje.nuevoRecorrido(origen);
			recorrido.estaciones.addAll(recorridoCopiaAuxiliar.estaciones);
			
			this.recorrerRamalHaciaAdelante(origen, destino, nuevoViaje, estacionConectada, losViajes);
			
			// realizamos el recorrido hacia atras en un ramal de una estacion conectada
			nuevoViaje = elViaje.clone();								
			nuevoViaje.recorridos.add(recorrido);
			recorrido=elViaje.nuevoRecorrido(estacionConectada);
			recorrido.estaciones.addAll(recorridoCopiaAuxiliar.estaciones);
			
			this.recorrerRamalHaciaAtras(origen, destino, nuevoViaje, estacionConectada,  losViajes);				
		}

	}
	
	
	/**
	 * metodo que recorre hacia adelante un ramal, si llega al destino agrega el viaje a la variable losViajes
	 *
	 * @author eugenio
	 * @param origen estacion origen
	 * @param destino estacion destino
	 * @param elViaje el viaje que se esta realizando, un arraylist de un arraylist  de estaciones
	 * @param estacionEnQueNosEncontramos estacion en la que estamos actualmente
	 * @param losViajes conjunto de viajes posibles que van de origen a destino
	 * @throws BusquedaException
	 * @throws ComoViajeException 
	 */
	private void recorrerRamalHaciaAdelante(Estacion origen, Estacion destino, Viaje elViaje, Estacion estacionEnQueNosEncontramos, Collection<Viaje> losViajes) throws BusquedaException, ComoViajeException {
		// corte de control segun enunciado
		if(this.corteDeControlGetViajes(elViaje)) {
			return;
		}

		// obtenemos el ramal de la estacion
		List<Estacion> ramal = this.getRamal(estacionEnQueNosEncontramos);
		
		Estacion estacion;
		Recorrido recorrido;
		
		// crear iterator para recorrer las estaciones del ramal
		ListIterator<Estacion> estacionesListIterator= ramal.listIterator();

	
		while (estacionesListIterator.hasNext()){
			estacion=estacionesListIterator.next();
			if(estacion.equals(estacionEnQueNosEncontramos)) break;
		}

		recorrido=elViaje.nuevoRecorrido(estacionEnQueNosEncontramos);
		estacionesListIterator.previous();
				
		// recorrer hacia adelante
		while (estacionesListIterator.hasNext()) {
			
			estacion = estacionesListIterator.next();
			
			recorrido.estaciones.add(estacion);
			
			// corte de control
			if(estacion.IsEqual(destino)){
				// llegamos, agregamos el recorrido al viaje
				elViaje.recorridos.add(recorrido);
				// agregamos el viaje al array que tiene todos los viajes posibles
				losViajes.add(elViaje);
				return;
			}
			
			if(!origen.equals(estacion)){
				Viaje nuevoViaje = elViaje.clone();
				Recorrido recorridosCopia=elViaje.nuevoRecorrido(estacion);
				recorridosCopia.estaciones.addAll(recorrido.estaciones);
				this.recorrerEstacionesConectadas(origen,destino,recorridosCopia,estacion,nuevoViaje,losViajes);
			}

			
		}
		
	}

	/**
	 * metodo que recorre hacia atras un ramal, si llega al destino agrega el viaje a la variable losViajes
	 *
	 * @author eugenio
	 * @param origen estacion origen
	 * @param destino estacion destino
	 * @param elViaje el viaje que se esta realizando, un arraylist de un arraylist  de estaciones
	 * @param estacionEnQueNosEncontramos estacion en la que estamos actualmente
	 * @param losViajes conjunto de viajes posibles que van de origen a destino
	 * @throws BusquedaException
	 * @throws ComoViajeException 
	 */
	private void recorrerRamalHaciaAtras(Estacion origen, Estacion destino, Viaje elViaje, Estacion estacionEnQueNosEncontramos, Collection<Viaje> losViajes) throws BusquedaException, ComoViajeException {
	 
		// corte de control segun enunciado
		if(this.corteDeControlGetViajes(elViaje)) {
			return;
		}
		// obtenemos el ramal de la estacion
		List<Estacion> ramal = this.getRamal(estacionEnQueNosEncontramos);
		
		Estacion estacion;
		Recorrido recorrido;
		
		// crear iterator para recorrer las estaciones del ramal
		ListIterator<Estacion> estacionesListIterator= ramal.listIterator();
		
		// poner el ramal en posicion para ser recorrido
		while (estacionesListIterator.hasNext()){
			estacion=estacionesListIterator.next();
			if(estacion.equals(estacionEnQueNosEncontramos)) break;
		}

		recorrido=elViaje.nuevoRecorrido(estacionEnQueNosEncontramos);
				
		// recorrer hacia adelante
		while (estacionesListIterator.hasPrevious()) {
			
			estacion = estacionesListIterator.previous();

			recorrido.estaciones.add(estacion);
			
			// corte de control
			if(estacion.IsEqual(destino)){
				// llegamos, agregamos el recorrido al viaje
				elViaje.recorridos.add(recorrido);
				// agregamos el viaje al array que tiene todos los viajes posibles
				losViajes.add(elViaje);
				return;
			}
			
			if(!origen.equals(estacion)){
				Viaje nuevoViaje = elViaje.clone();
				Recorrido recorridosCopia=elViaje.nuevoRecorrido(estacion);
				recorridosCopia.estaciones.addAll(recorrido.estaciones);
				this.recorrerEstacionesConectadas(origen,destino,recorridosCopia,estacion,nuevoViaje,losViajes);
			}
		}
		
	}
}